using System.IO;
using Debug;
using Kristall;
using Kristall.Screens;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System;
using System.ComponentModel;
using Microsoft.Xna.Framework.Audio;
using Kristall.Managers;

namespace Kristall
{
    public class Kristall : Microsoft.Xna.Framework.Game
    {
        #region Fields And Properties
        public static GraphicsDeviceManager GraphicsDeviceManager;
        public static ScreenManager ScreenManager;
        public static Options Options;
        public static DebugManager DebugManager;
        public static SoundManager SoundManager;
        public static Texture2D Blank;

        public static bool EnableDebug
        {
            get
            {
                return Options.debug;
            }
        }

        public static IDebugCommandHost Console
        {
            get
            {
                return (IDebugCommandHost)Kristall.ScreenManager.Game.Services.GetService(typeof(IDebugCommandHost));
            }
        }

        public static char DS
        {
            get
            {
                return Path.DirectorySeparatorChar;
            }
        }

        public static Vector2 Viewport
        {
            get
            {
                return new Vector2(ScreenManager.GraphicsDevice.Viewport.Width, ScreenManager.GraphicsDevice.Viewport.Height);
            }
        }

        public static string FullContentPath
        {
            get
            {
                return Application.StartupPath + Kristall.DS + "Content";
            }
        }

        public static SpriteFont SmallFont { get; set; }

        #endregion

        void ResetVideo()
        {
            GraphicsDeviceManager.IsFullScreen = Options.gfx_fullscreen;
            GraphicsDeviceManager.PreferredBackBufferWidth = Options.gfx_screenwidth;
            GraphicsDeviceManager.PreferredBackBufferHeight = Options.gfx_screenheight;

            GraphicsDeviceManager.ApplyChanges();
        }

        public Kristall()
        {
            GraphicsDeviceManager = new GraphicsDeviceManager(this);

            Content.RootDirectory = "Content";

            DebugManager = new DebugManager(this, "console");
            ScreenManager = new ScreenManager(this);

            ScreenManager.AddScreen(new BackgroundScreen());
            ScreenManager.AddScreen(new MainMenuScreen());

            SoundManager = new SoundManager(this);

            Components.Add(ScreenManager);
            Components.Add(DebugManager);
            Components.Add(SoundManager);

            Window.Title = Assembly.GetExecutingAssembly().GetName().Version.ToString();
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            Options = Options.Load();

            ResetVideo();

            IsMouseVisible = Options.show_mouse;

            Blank = Content.Load<Texture2D>(@"blank");

            SmallFont = Content.Load<SpriteFont>(@"SmallFont");

            //Setup console commands for all properties in options

            //Fetch all properties set in the Options class
            PropertyInfo[] pi = Options.GetType().GetProperties();

            //Loop through all of the options and add commands to the console
            for (int i = 0; i < pi.Length; i++)
            {
                //Fetch description of property
                PropertyInfo prop = pi[i];
                DescriptionAttribute descriptionAttr = Attribute.GetCustomAttribute(prop, typeof(DescriptionAttribute)) as DescriptionAttribute;

                string description = "";
                if(descriptionAttr != null)
                    description = descriptionAttr.Description;

                //Declare command
                DebugCommandExecute del = delegate(IDebugCommandHost host, string command, IList<string> args)
                {
                    string value = string.Join("", args);

                    //Check if the argument is empty
                    if (string.IsNullOrWhiteSpace(value))
                    {
                        string propValue = prop.GetValue(Options, null).ToString();
                        Console.Echo(DebugConsole.CCCyan + command + DebugConsole.CCWhite + ":" + propValue);
                    }
                    else
                    {
                        //Get the type of property (ex: int, float etc)
                        Type propType = prop.PropertyType;

                        if (propType.Equals(typeof(String)))
                        {
                            //If the type is String, just assign the value
                            prop.SetValue(Options, value, null);
                        }
                        else
                        {
                            //First try to find a method with name "parse<TypeName>"
                            MethodInfo method = Options.GetType().GetMethod("parse" + propType.Name);
                            if (method != null)
                            {
                                var parameters = new object[] { value };
                                object result = method.Invoke(null, parameters);
                                prop.SetValue(Options, result, null);
                            }
                            else
                            {
                                //If there was no such method, try and use the types TryParse method
                                BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Static;
                                Type[] parameterTypes = new Type[] { typeof(string), propType.MakeByRefType() };
                                method = propType.GetMethod("TryParse", bindingFlags, null, parameterTypes, null);
                                    
                                if(method != null)
                                {
                                    object[] parameters = new object[] { value, null };
                                    bool success = (bool)method.Invoke(null, parameters);
                                    object result = null;

                                    if (!success)
                                    {
                                        Echo(DebugConsole.CCRed + "Error: Couldn't convert value: " + value + " to " + propType.Name);
                                    }
                                    else
                                    {
                                        result = parameters[1];
                                        prop.SetValue(Options, result, null);
                                    }
                                }
                                else
                                {
                                    //If neither method was found, we couldnt convert the value
                                    Echo(DebugConsole.CCRed + "Error: Couldn't find parse method for type: " + propType.Name);
                                }
                            }
                        }
                    }
                };

                Console.RegisterCommand(prop.Name, description, del);
            }

            Console.RegisterCommand("print_screens", "Prints a list of screens", delegate(IDebugCommandHost host, string command, IList<string> args)
            {
                foreach (GameScreen screen in ScreenManager.GetScreens())
                    Echo(screen.ToString());
            });

            Console.RegisterCommand("cfg_save", "Save the current config", delegate(IDebugCommandHost host, string command, IList<string> args)
            {
                if (args.Any())
                {
                    Options.Save(args[0]);
                }
                else
                {
                    Options.Save();
                }
            });

            Console.RegisterCommand("cfg_load", "Load a config", delegate(IDebugCommandHost host, string command, IList<string> args)
            {
                if (args.Any())
                {
                    Kristall.Options = Options.Load(args[0]);
                }
                else
                {
                    Options.Load();
                }
            });
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            Content.Unload();
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == Microsoft.Xna.Framework.Input.ButtonState.Pressed)
            { 
                this.Exit();
            }

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Black);
            base.Draw(gameTime);
        }

        public static void Echo(string text)
        {
            if (Console != null)
            { 
                Console.Echo(text);
            }
        }

        public static Type[] GetExtraTypes()
        {
            string[] namespaces = new[]{
                    "Kristall.MyGame",
                    "Kristall.Engine",
                };

            string[] ignoreNamespaces = new[]{
                    "Kristall.MyGame.Behaviors",
                    "Kristall.Engine.Behaviors",
                    "Kristall.MyGame.Projectiles",
                };

            string[] ignoreClasses = new[] {
                    "AABBExtensions"
                };

            Assembly assembly = Assembly.GetExecutingAssembly();
            Type[] typesInAssembly = assembly.GetTypes();
            List<Type> extraTypes = typesInAssembly.Where(delegate(Type t)
            {
                if (t.IsClass && t.IsPublic && !ignoreClasses.Contains(t.Name) && t.Namespace != null && !ignoreNamespaces.Contains(t.Namespace) && namespaces.Any(n => t.Namespace.StartsWith(n)))
                    return true;
                return false;
            }).ToList();

            return extraTypes.ToArray();
        }
    }
}
